<template>
    <div class="flex-box">
        <div class="flex-header">
            <button class="sea-btn-icon" @click="copy" title="复制当前代码">
                <i class="fa fa-fw fa-copy"></i>
            </button>
            <button class="sea-btn-icon" @click="toggleDark" title="夜间/白天风格">
                <i class="fa fa-fw fa-lightbulb-o"></i>
            </button>

            <i class="divider-vertical margin-right"> </i>

            <h5> {{ props.title }}</h5>
        </div>
        <div class="flex-body">
            <div class="scroll" style="height: 100%">
                <div ref="codeRef"></div>
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import {onMounted, ref, watch} from 'vue';
import highlight from 'highlight.js';
import 'highlight.js/styles/vs.css';
import 'highlight.js/styles/stackoverflow-dark.css';
import FileUtils from "@/lang/commons/file-utils";

/**
 * 代码高亮展示
 *
 * 这个组件有两个参数，同时修改的时候，要先设置 type，再设置 text，
 * 我们只做了一个参数的侦听，只有在修改了 text 的时候，才会触发侦听。
 *
 * 保持习惯就好，不要尝试优化，这是为了避免重复触发 reload() 函数。
 */
interface CodeBlockProps {
    /** 代码类型：javascript, xml, html... */
    type?: string;
    /** 代码内容 */
    text?: string;
    /** 标题 */
    title?: string;
    /** 代码是否已经转义 */
    encode?: boolean;
}

const props = withDefaults(defineProps<CodeBlockProps>(), {
    type: 'html',
    text: '',
    title: '',
    encode: false
});

const codeRef = ref();

// 监听 text 属性的变化
watch(() => props.text, () => {
    reload();
});

/**
 * 字符转义
 *
 * @param unsafe {string} -
 * @returns {string} -
 */
const escape = (unsafe) => {
    return unsafe
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;")
            .replace(/'/g, "&#039;");
}

/**
 * 字符反转义
 *
 * @param safe {string} - 已转义的字符串
 * @returns {string} - 反转义后的原始字符串
 */
const unescape = (safe) => {
    return safe
            .replace(/&amp;/g, "&")
            .replace(/&lt;/g, "<")
            .replace(/&gt;/g, ">")
            .replace(/&quot;/g, '"')
            .replace(/&#039;/g, "'");
};

/**
 * 将当前的代码粘贴到剪切板
 */
const copy = () => {
    if (props.encode) {
        FileUtils.writeTextToClipboard(unescape(props.text));
    } else {
        FileUtils.writeTextToClipboard(props.text);
    }
}

// 文本风格
let dark = true;
const black = '#303133', white = '#f3f4f4';
const toggleDark = () => {
    codeRef.value.querySelectorAll('pre code').forEach(block => {
        if (dark) {
            block.style.backgroundColor = white;
            block.style.color = black;
        } else {
            block.style.backgroundColor = black;
            block.style.color = white;
        }
    })
    dark = !dark;
}

/**
 * 代码高亮并增加行号
 *
 * @param text 未转义的代码
 * @param type 代码类型
 */
const highlightWithNumber = (text, type = 'javascript') => {
    // 代码不需要转义，如果转义了，需要还原
    const code = props.encode ? unescape(text) : text;

    // 紧凑的代码
    const highlighted = highlight.highlight(code, {language: type}).value;
    const lines = highlighted.split('\n');

    // 重新拼接 html 脚本
    let ret = [];
    lines.forEach((line, index) => {
        ret.push(`<div class="line-number-container"><span class="line-number">${index + 1}</span><pre class="line-content">${line}</pre></div>`);
    });

    // 添加到页面
    codeRef.value.innerHTML = `<pre class="code no-margin"><code class="${type} hljs language-${type}">${ret.join('')}</code></pre>`;
}

/**
 * 标准代码高亮实现
 *
 * @param text 未转义的代码
 * @param type 代码类型
 */
const highlightStandard = (text, type = 'javascript') => {
    // 代码需要转义
    const code = props.encode ? text : escape(text);
    codeRef.value.innerHTML = `<pre class="code"><code class="${type}">${code}</code></pre>`;
    codeRef.value.querySelectorAll('pre code').forEach(block => {
        highlight.highlightElement(block);
    });
}

// 重新加载代码高亮的方法
const reload = () => {
    if (codeRef.value) {
        highlightWithNumber(props.text, props.type);
    }
};

// 组件挂载后执行的方法
onMounted(() => {
    if (props.text !== undefined) {
        reload();
    }
});
</script>

<style scoped>
</style>